package org.geppeto.eva.synchronizer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.cometd.Bayeux;
import org.cometd.Client;

public class InstanceManager {
	
	private HashMap _instance;
	private SyncService _pubSub;
	public static String TOKEN_CALLBACK = "local-package:token:";
	
	
	//BEGIN Creating singleton object	
	private static InstanceManager ref;	
	private InstanceManager(){
		_instance = new HashMap();
	}	
	public static InstanceManager getInstanceManager() {
		if (ref == null)
	        // it's ok, we can call this constructor
			ref = new InstanceManager();
		return ref;
	}
	public Object clone()throws CloneNotSupportedException
	{
		throw new CloneNotSupportedException();

	}
	//END Creating singleton object	

	
	public void setPubSub(SyncService pubSub) {
		_pubSub = pubSub;
	}

	protected String addNew(String key, String mechanism){
		try{
			//TODO check what if it exists - in debugging
			if (mechanism.equals("semaphore"))
				_instance.put(key, new Semaphore(this));
			else if (mechanism.equals("mailbox"))
				_instance.put(key, new Mailbox(this));
			else
			if (mechanism.equals("token")){
				Semaphore sem = new Semaphore(this, 0);
				Mailbox mb = new Mailbox(this);
				_instance.put(key + "/sem", sem);
				_pubSub.RegisterChannel(key + "/sem");
				_instance.put(key + "/mb", mb);
				_pubSub.RegisterChannel(key + "/mb");				
				_instance.put(key, new Token(sem, mb, this, key));
				mb.getMessage(TOKEN_CALLBACK + key);
			}
			_pubSub.RegisterChannel(key);
			return "Create " + key;
		}
		catch(Exception ex){
			return "Failed";
		}
	}
	
	protected String addNew(String key, String mechanism, int time, int minParticipants, int maxParticipants){
		try{
			//TODO check what if it exists - in debugging
			if (mechanism.equals("event")){
				Event event = new Event(this, key, time, minParticipants, maxParticipants);
				_instance.put(key, event);
				_pubSub.RegisterChannel(key);
			}
			return "Created " + key;
		}
		catch(Exception ex){
			return "Failed";
		}
	}
	
	protected String delete(String key){
		try{
			if (key.contains("token") || key.contains("event")){
				_instance.remove(key + "/sem");
				_instance.remove(key + "/mb");
			}
			_instance.remove(key);
			//TODO remove channel from _pubSub
			return "Delete " + key;
		}
		catch(Exception ex){
			return "Failed";
		}
	}
	
	protected String acquire(String key, String userID, String mechanism){
		try{
			Semaphore sem;
			sem = (Semaphore)_instance.get(key);
			String result="";
			synchronized (sem) {
				result= sem.acquire(userID);
				//Publish to channel for this instance
				_pubSub.Notify(key, result);				
			}
			return "Acquire " + key + " " + result;
		}
		catch(Exception ex){
			return "Failed";
		}
	}
	
	protected String release(String key, String userID, String mechanism){
		try{
			Semaphore sem;
			String result="";
			sem = (Semaphore)_instance.get(key);
			synchronized (sem) {
				result = sem.release(userID);
				//Publish
				_pubSub.Notify(key, result);				
			}
			return "Released " + key + " " + result;
		}
		catch(Exception ex){
			return "Failed";
		}		
	}

	protected String getMsg(String key, String userID){
		try{
			Mailbox mbx;
			mbx = (Mailbox)_instance.get(key);
			String result ="";
			synchronized (mbx) {
				result= mbx.getMessage(userID);
				//Publish
				_pubSub.Notify(key, result);
			}
			return "Get message " + key + " " + result;
		}
		catch(Exception ex){
			return "Failed";
		}		
	}

	protected String putMsg(String key, String userID, String msg){
		try{
			Mailbox mbx;
			mbx = (Mailbox)_instance.get(key);
			String result = "";
			synchronized (mbx) {
				result = mbx.putMessage(userID, msg);
				//Publish
				_pubSub.Notify(key, result);
			}
			return "Put message " + key + " " + result;
		}
		catch(Exception ex){
			return "Failed";
		}		
	}

	protected String takeNmbr(String key, String userID){
		try{
			Token tok;			
			tok = (Token)_instance.get(key);			
			HashMap result;
			synchronized (tok) {
				result = tok.takeNmbr(userID);
				//Publish ?what 
				//Publish on channels: sem, mb
				//Return tokenNmbr via REST to the one who asked for it
				//and publish when one leaves with "bye" from desk officer
				_pubSub.Notify(key + "/sem", (String)result.get("sem"));
				_pubSub.Notify(key + "/mb", (String)result.get("mb"));
			}
			return (String)result.get("tok");
		}
		catch(Exception ex){
			return "Failed";
		}		
	}
	
	protected String callbackToken(String key, String msg){
		try{
			String result="";
			Token tok;
			tok = (Token)_instance.get(key);	
			//This method may not use synchronized, because tok instance is already locked
			
			//Ako ceka dolazak novog broja, onda treba otkazati to cekanje
			// jer se pojavio ovaj koji kasni, a nitko drugi nije dosao u medjuvremenu			
			if (msg.startsWith("getOut"))
				//Do not publish what was retrieved from MB, 
				// it is just a message to remove reader from waiting into active state
				// so just return
				return "OK";
			result = tok.callbackToken(msg, key);
			_pubSub.Notify(key, result);
			return result;
		}
		catch(Exception ex){
			return "Failed";
		}		
	}
	
	protected String bye(String key, String userID){
		try{
			Token tok;
			tok = (Token)_instance.get(key);
			HashMap result;
			synchronized (tok) {
				result = tok.bye(userID, key);			
				//Publish ?what, just return via REST, and publish when called from desk
				_pubSub.Notify(key, (String)result.get("tok"));
				_pubSub.Notify(key + "/mb", (String)result.get("mb"));
			}
			return "Bye";
		}
		catch(Exception ex){
			return "Failed";
		}
	}
	
	public String hello(String key, String userID, String nmbr) {
		try{
			String result = "";
			Token tok;
			tok = (Token)_instance.get(key);
			synchronized (tok) {
				result = tok.hello(userID, key, nmbr);
			}
			return result;
		}
		catch(Exception ex){
			return "Failed";
		}
	}
	
	public String getCounter(String key, String userId){
		try {
			Token tok;
			tok = (Token)_instance.get(key);
			return tok._sem.getInfo();
		}catch(Exception ex){
			return "Failed";
		}
	}
	
	private boolean alreadyExists(String key){
		try{

			return true;
		}
		catch(Exception ex){
			return true;
		}
	}
	
	public String registerUser ( String key, String userID ){
		try {
			Mechanism mechanism;
			mechanism = (Mechanism)_instance.get(key);
			String response;
			if (mechanism.userRegister(userID)){
				// notify subscribers
				Map<String, Object> publicResponse = mechanism.getInfoObject();
				_pubSub.NotifyObj(key, publicResponse);
				
				response = "{isRegistered: true}";
			} else {
				response = "{isRegistered: false, description: 'eventFull'}";
			}
			return response;
		}catch(Exception ex){
			return "Failed to register.";
		}
	}

	public String unregisterUser ( String key, String userID ){
		try {
			Mechanism mechanism;
			mechanism = (Mechanism)_instance.get(key);
			String response;
			if (mechanism.userUnregister(userID)){
				// notify subscribers
				Map<String, Object> publicResponse = mechanism.getInfoObject();
				_pubSub.NotifyObj(key, publicResponse);
				
				response = "{isRegistered: false}";
			} else {
				response = "{isRegistered: true, description: 'error'}";
			}
			return response;
		}catch(Exception ex){
			return "Failed to unregister.";
		}
	}

	public String getInfo ( String key ){
		try {
			Mechanism mechanism;
			mechanism = (Mechanism)_instance.get(key);
			String response = mechanism.getInfo();
			return response;
		}catch(Exception ex){
			return "Failed to get info.";
		}
	}

	public void sendEventStatus ( String key ){
		try {
			Event event;
			event = (Event)_instance.get(key);
			Map<String, Object> publicResponse = event.getEventStatus();
			if(!publicResponse.isEmpty()) _pubSub.NotifyObj(key, publicResponse);
		}catch(Exception ex){}
	}

	public String addAlert ( String key, String user, int time ){
		try {
			Event event;
			event = (Event)_instance.get(key);
			String response;
			if(event.addAlert(user, time)){
				String userChannel = key + "/" + user;
				_pubSub.RegisterChannel(userChannel);
				response = "{isAlertAdded: true, channel: '" + userChannel + "'}";
			} else {
				response = "{isAlertAdded: false}";
			}
			return response;
		}catch(Exception ex){
			return "Failed to update time.";
		}
	}

	public void alertUser ( String key, String user ){
		try {
			String userChannel = key + "/" + user;
			Map<String, Object> response = Event.userAlert(user);
			_pubSub.NotifyObj(userChannel, response);
		}catch(Exception ex){}
	}
	
}
